/*
 *  jsruntime.c
 *  CompiledJavascriptTest
 *
 *  Created by Andrew Barry on 28/06/09.
 *  Copyright 2009 Barry Computer Services. All rights reserved.
 *
 */
#ifdef MACOSX
#include <cocoa/Cocoa.h>
#include "objc.h"
#endif
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "jsruntime.h"
#import "form.h"
#import "timer.h"
#ifndef WIN32
#import "control.h"
#import "DebugClient.h"
#endif
#import "mersenneTwister.h"

void unimplemented();
void registerFile(jscontext *cx);
void registerTask(jscontext *cx);
void registerGraphics(jscontext *cx);
void registerUrl(jscontext *cx);
void registerXML(jscontext *cx);
//void registerJavaBridge(jscontext *cx);

int compareStrings(jsstring *a, jsstring *b);
int equalStrings(jsstring *a, jsstring *b);
int equalStringsJC(jsstring *a, const char *b);

#ifdef MACOSX
extern NSDictionary *gInitBreakpoints;
static NSMutableArray *captureStack(jscontext *cx);
#endif

jsvalue jsstrn(jscontext *cx, unsigned short *strval, int len);

extern int stepMode;
int stepDepth;


struct jsroot {
	struct jsroot *next;
	jsvalue *ptr;
	int count;
};

struct jsstackroot {
	struct jsstackroot *next;
	jsvalue val;
};

struct jsarray
{
	struct jsobject base;
	int count;
	jsvalue *content;
};

struct jsdouble
{
	int flags;
	double v;
	struct jsdouble *prevDouble, *nextDouble;
};

jsobject *newObject(jscontext *cx);

#ifndef WIN32
NSMutableDictionary *keyMapper;

id idFromJSVal(jscontext *cx, jsvalue v) {
	return (id) (v & ~1);
}

id idFromObject(jscontext *cx, jsvalue obj) {
	return (id) (jsget(cx, obj, 0, "_id") & ~1);
}

void idToObject(jscontext *cx, jsvalue obj, id val) {
	jsset(cx, obj, 0, "_id", (int) val | 1);
}

const char *keyFromString(NSString *str) {
	if (!keyMapper)
		keyMapper = [NSMutableDictionary new];
	NSValue *v = [keyMapper objectForKey:str];
	if (v != nil)
		return [v pointerValue];
	const char *cstr = strdup([str UTF8String]);
	[keyMapper setObject:[NSValue valueWithPointer: cstr] forKey:str];
	return cstr;
}
#endif

void *fmalloc(jscontext *cx, int len) 
{
	if (len & 7) {
		len += (8 - (len & 7));
	}
	cx->memusage += len;
	if (len > cx->mallocBufferLen) {
		cx->mallocBufferLen = 8192;
		cx->mallocBuffer = malloc(cx->mallocBufferLen);
	}
	void *rv = cx->mallocBuffer;
	cx->mallocBuffer += len;
	cx->mallocBufferLen -= len;
	return rv;
}

static void initObject(jscontext *cx, jsobject *obj, jsfunction *cls, const char *origin) {
	obj->flags = 0;
	obj->cls = cls;
	obj->nextObject = cx->firstObject;
	obj->prevObject = NULL;
	obj->origin = origin;
	obj->flags = 0;
	obj->fields = NULL;
	cx->objcount++;
	
	if (cx->firstObject)
		cx->firstObject->prevObject = obj;
	cx->firstObject = obj;
}

void jserror(jscontext *cx, const char *message) {
#ifdef MACOSX
	if (cx->topOfStack)
		debugError(captureStack(cx), message, cx->topOfStack->source, cx->topOfStack->line);
	else
		debugError(nil, message, nil, 0);
#endif
	printf("%s\n", message);
	exit(1);
}

int jsisnum(jsvalue v) {
	return v != JSVAL_NULL && (JSVAL_IS_DOUBLE(v) || JSVAL_IS_INT(v));
}

double jsasnum(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v))
		return JSVAL_TO_DOUBLE(v)->v;
	else if (JSVAL_IS_INT(v))
		return JSVAL_TO_INT(v);
	else {
		return 0;
	}

}

#ifdef MACOSX
NSString *jsasstrRetain(jscontext *cx, jsvalue v) {
	if (!v)
		return [@"undefined" retain];
	if (JSVAL_IS_DOUBLE(v)) {
		return [[NSString alloc] initWithFormat:@"%f", jsasnum(cx, v)];
	} else if (JSVAL_IS_INT(v)) {
		return [[NSString alloc] initWithFormat:@"%d", JSVAL_TO_INT(v)];
	} else if (JSVAL_IS_STRING(v)) {
		jsstring *str = JSVAL_TO_STRING(v);
		if (!str->nsstr)
			str->nsstr = [[NSString stringWithCharacters:str->chars length:str->length] retain];
		return [str->nsstr retain];
	} else if (JSVAL_IS_OBJECT(v)) {
		return [@"[Object]" retain];
	} else if (JSVAL_IS_BOOLEAN(v)) {
		if (JSVAL_TO_BOOLEAN(v))
			return [@"true" retain];
		else
			return [@"false" retain];
	}
	return [@"???" retain];
}
#endif

jsstring *jsasstr(jscontext *cx, jsvalue v) {
	if (!v)
		return JSVAL_TO_STRING(jsstrFromConst(cx, L"undefined"));
	if (JSVAL_IS_DOUBLE(v)) {
		char buffer[40];
		sprintf(buffer, "%g",jsasnum(cx, v));
		return JSVAL_TO_STRING(jsstrFromCstring(cx, buffer));
	} else if (JSVAL_IS_INT(v)) {
		char buffer[40];
		sprintf(buffer, "%d",JSVAL_TO_INT(v));
		return JSVAL_TO_STRING(jsstrFromCstring(cx, buffer));
	} else if (JSVAL_IS_STRING(v)) {
		return JSVAL_TO_STRING(v);
	} else if (JSVAL_IS_OBJECT(v)) {
		return JSVAL_TO_STRING(jsstrFromConst(cx, L"[Object]"));
	} else if (JSVAL_IS_BOOLEAN(v)) {
		if (JSVAL_TO_BOOLEAN(v))
			return JSVAL_TO_STRING(jsstrFromConst(cx, L"true"));
		else
			return JSVAL_TO_STRING(jsstrFromConst(cx, L"false"));
	}
	return JSVAL_TO_STRING(jsstrFromConst(cx, L"???"));
}

int jsasint(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		return JSVAL_TO_DOUBLE(v)->v;
	} else if (JSVAL_IS_INT(v)) {
		return JSVAL_TO_INT(v);
	} else if (JSVAL_IS_BOOLEAN(v)) {
		return JSVAL_TO_BOOLEAN(v);
	} else {
		jserror(cx, "cannot convert value to integer");
		return 0;
	}
}

jsbool jsasbool(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_BOOLEAN(v))
		return JSVAL_TO_BOOLEAN(v);
	if (JSVAL_IS_INT(v))
		return JSVAL_TO_INT(v) != 0;
	if (JSVAL_IS_DOUBLE(v))
		return JSVAL_TO_DOUBLE(v)->v != 0;
	jserror(cx, "cannot convert value to boolean");
	return 0;
}

jsvalue jssqrt(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	if (jsisnum(args[0])) {
		return jsnum(cx, sqrt(jsasnum(cx, args[0])));
	}
	return args[0];
}

jsvalue jsabs(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	if (jsisnum(args[0])) {
		return jsnum(cx, abs(jsasnum(cx, args[0])));
	}
	return args[0];
}

jsvalue jsround(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	if (jsisnum(args[0])) {
		return jsnum(cx, round(jsasnum(cx, args[0])));
	}
	return args[0];
}

jsvalue jscos(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	
	return jsnum(cx, cos(jsasnum(cx, args[0])));
}

jsvalue jssin(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	return jsnum(cx, sin(jsasnum(cx, args[0])));
}

jsvalue mathFloor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, 0);
	return jsnumi(cx, floor(jsasnum(cx, args[0])));
}

jsvalue mathRandom(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return jsnum(cx, mtd_random());
}

jsvalue runtimeDebugstr(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	char buffer[1000];
	int pos = 0;
	for (int idx = 0; idx < argc; idx++) {
		jsstring *str = jsasstr(cx, args[idx]);
		for (int j = 0; j < str->length; j++) {
			unsigned short ch = str->chars[j];
			if (ch < 128)
				buffer[pos++] = ch;
			else if (ch < 0x7ff) {
				buffer[pos++] = 192 | (ch >> 6);
				buffer[pos++] = 128 | (ch & 0x3f);
			} else {
				buffer[pos++] = 224 | (ch >> 12);
				buffer[pos++] = 128 | ((ch >> 6) & 0x3f);
				buffer[pos++] = 128 | (ch & 0x3f);
			}
			if (pos > 900) {
				buffer[pos] = 0;
				printf("%s", buffer);
				pos = 0;
			}
		}
	}
	buffer[pos] = 0;
	printf("%s\n", buffer);
	pos = 0;
	
	return JSVAL_VOID;
}

static jsvalue arrayConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue objectConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue stringConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue functionConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue dateConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	double millis;
	if (argc == 1 && jsisnum(args[0])) {
		millis = jsasnum(cx, args[0]);
	} else {
		struct timeval tm;
		gettimeofday(&tm, nil);
		millis = tm.tv_sec * 1000.0 + tm.tv_usec / 1000.0;
	}
	
	jsset(cx, me, 0, "_time", jsnum(cx, millis));
	return JSVAL_VOID;
}

static void dateDestructor(jscontext *cx, jsvalue v) {
#ifdef MACOSX
	jsvalue cache = jsget(cx, v, 0, "_local");
	if (cache) {
		NSDateComponents *dc = (NSDateComponents *) (cache & ~1);
		[dc release];
	}		
#endif
}

static jsvalue dateGetTime(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return jsget(cx, me, 0, "_time");
}

#ifdef MACOSX
NSDateComponents *getDateComponents(jscontext *cx, jsvalue me) {
	jsvalue cache = jsget(cx, me, 0, "_local");
	if (cache) {
		return (NSDateComponents *) (cache & ~1);
	}
	double time = jsgetdouble(cx, me, 0, "_time", 0);
	NSDate *dt = [NSDate dateWithTimeIntervalSince1970:time / 1000];
	NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
	int components = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit;
	NSDateComponents *dc = [gregorian components:components fromDate:dt];
	
	[gregorian release];
	
	[dc retain];
	jsset(cx, me, 0, "_local", (int) dc | 1);
	
	return dc;
}

static jsvalue dateGetMilliseconds(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	double t = jsgetdouble(cx, me, 0, "_time", 0);
	return jsnum(cx, t - floor(t / 1000) * 1000);
}

static jsvalue dateGetSeconds(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	NSDateComponents *dt = getDateComponents(cx, me);
	return jsnumi(cx, [dt second]);
}

static jsvalue dateGetHours(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	NSDateComponents *dt = getDateComponents(cx, me);
	return jsnumi(cx, [dt hour]);
}

static jsvalue dateGetMinutes(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	NSDateComponents *dt = getDateComponents(cx, me);
	return jsnumi(cx, [dt minute]);
}
#endif

static jsvalue arrayPush(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (!isArray(me))
		return JSVAL_VOID;
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(me);
	for (int idx = 0; idx < argc; idx++)
		jssetelem(cx, me, INT_TO_JSVAL(arr->count), args[idx]);
	return me;
}

static jsvalue arrayClear(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (!isArray(me))
		return JSVAL_VOID;
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(me);
	arr->count = 0;
	free(arr->content);
	arr->content = 0;
	return me;
}

static jsvalue arrayLength(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (!isArray(me))
		return JSVAL_VOID;
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(me);
	return jsnumi(cx, arr->count);
}

static jsvalue arraySplice(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (!isArray(me) || argc < 2)
		return JSVAL_VOID;
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(me);

	int start = jsasint(cx, args[0]);
	int toRemove = jsasint(cx, args[1]);
	
	if (start < 0) {
		toRemove += start;
		start = 0;
	}
	if (toRemove < 0)
		toRemove = 0;
	if (start >= arr->count) {
		start = arr->count;
		toRemove = 0;
	}
	if ((toRemove + start) > arr->count) {
		toRemove = arr->count - start;
	}
	
	int delta = argc - 2 - toRemove;
	
	// shift if necessary
	if (delta < 0) {
		for (int idx = start + toRemove; idx < arr->count; idx++)
			arr->content[idx + delta] = arr->content[idx];
		arr->content = realloc(arr->content, (arr->count + delta) * sizeof(jsvalue));
	} else if (delta > 0) {
		arr->content = realloc(arr->content, (arr->count + delta) * sizeof(jsvalue));
		for (int idx = arr->count - 1; idx >= start + toRemove; idx--) {
			arr->content[idx + delta] = arr->content[idx];
		}
	}
	arr->count += delta;
	// copy in new items
	for (int idx = 0; idx < argc - 2; idx++) {
		arr->content[idx + start] = args[idx + 2];
	}
	
	return me;
}

static jsvalue functionCallMethod(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	return jsinvoke(cx, me, args[0], args + 1, argc - 1);
}

int charIsDigit(unsigned short ch) {
	return ch >= '0' && ch <= '9';
}

int charIsWhitespace(unsigned short ch) {
	return ch ==' ' || ch == '\n' || ch == '\r' || ch == '\t';
}

jsvalue parseInt(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc == 0)
		return INT_TO_JSVAL(0);

	if (JSVAL_IS_STRING(args[0])) {
		jsstring *mestr = JSVAL_TO_STRING(args[0]);
		int rval = 0;
		int pos = 0;
		int negate = 0;
		while (pos < mestr->length && charIsWhitespace(mestr->chars[pos]))
			pos++;
		if (pos < mestr->length && mestr->chars[pos] == '-') {
			negate = 1;
			pos++;
		}
		while (pos < mestr->length && charIsDigit(mestr->chars[pos])) {
			rval = rval * 10 + (mestr->chars[pos] - '0');
			pos++;
		}
		if (negate)
			return jsnumi(cx, -rval);
		else
			return jsnum(cx, rval);
	} else
		jserror(cx, "parseInt only operates on strings");

	return INT_TO_JSVAL(0);
}

jsvalue stringIndexOf(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, -1);
	
	if (JSVAL_IS_STRING(me) && JSVAL_IS_STRING(args[0])) {
		jsstring *mestr = JSVAL_TO_STRING(me);
		jsstring *term = JSVAL_TO_STRING(args[0]);
		unsigned short *mechars = mestr->chars;
		unsigned short *termchars = term->chars;
		
		int pos = 0;
		
		if (argc >= 2)
		{
			int startPos = jsasint(cx, args[1]);
			if (startPos > 0)
				pos = startPos;
		}
		
		while (pos <= (mestr->length - term->length)) {
			unsigned short *b = mechars + pos;
			char match = 1;
			for  (int idx = 0; idx < term->length; idx++)
				if (b[idx] != termchars[idx])
				{
					match = 0;
					break;
				}
			if (match)
				return jsnumi(cx, pos);
			pos++;
		}
	}
	
	return jsnumi(cx, -1);
}

jsvalue stringLastIndexOf(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return jsnumi(cx, -1);
	
	if (JSVAL_IS_STRING(me) && JSVAL_IS_STRING(args[0])) {
		jsstring *mestr = JSVAL_TO_STRING(me);
		jsstring *term = JSVAL_TO_STRING(args[0]);
		unsigned short *mechars = mestr->chars;
		unsigned short *termchars = term->chars;
		
		int pos = mestr->length - term->length;
		
		if (argc >= 2)
		{
			int startPos = jsasint(cx, args[1]);
			if (startPos <= pos)
				pos = startPos;
		}
		
		while (pos >= 0) {
			unsigned short *b = mechars + pos;
			char match = 1;
			for  (int idx = 0; idx < term->length; idx++)
				if (b[idx] != termchars[idx])
				{
					match = 0;
					break;
				}
			if (match)
				return jsnumi(cx, pos);
			pos--;
		}
	}
		
	return jsnumi(cx, -1);
}

jsvalue stringSubstring(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1 || !JSVAL_IS_STRING(me))
		return me;
	
	int start = jsasint(cx, args[0]);
	jsstring *mestr = JSVAL_TO_STRING(me);
	
	if (argc == 1)
	{
		if (start <= 0)
			return me;
		if (start >= mestr->length)
			return jsstrn(cx, 0, 0);
		return jsstrn(cx, mestr->chars + start, mestr->length - start);
	}
	else {
		int stop = jsasint(cx, args[1]);
		if (start >= stop || start >= mestr->length || stop <= 0)
			return jsstrn(cx, 0, 0);
		return jsstrn(cx, mestr->chars + start, stop - start);
	}
}


jsvalue stringLength(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (JSVAL_IS_STRING(me))
		return jsnumi(cx, JSVAL_TO_STRING(me)->length);
	return 0;
}

jsvalue stringCharAt(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (JSVAL_IS_STRING(me) && argc >= 1) {
		jsstring *mestr = JSVAL_TO_STRING(me);
		int pos = jsasint(cx, args[0]);
		if (pos >= 0 && pos < mestr->length) {
			unsigned short *chars = malloc(2);
			chars[0] = mestr->chars[pos];
			return jsstrFromChars(cx, chars, 1);
		}
	}
	return 0;
}

jsvalue appVersion(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
#ifdef MACOSX
	NSString *ver = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
	if (ver)
		return jsstrFromNSString(cx, ver);
#endif

	return 0;
}


jscontext *jsnewcontext(int slotCount) {
	jscontext *cx = malloc(sizeof(jscontext));
	memset(cx, 0, sizeof(jscontext));
	cx->slotCount = slotCount;
	cx->slots = malloc(slotCount * sizeof(jsvalue));
	memset(cx->slots, 0, slotCount * sizeof(jsvalue));
	return cx;
}

jscontext *jsnewdebugcontext(int slotCount, int breakpointCount, short *breakpointLines, const char **breakpointScripts) {
	jscontext *cx = jsnewcontext(slotCount);

	cx->breakpointFlags = malloc(breakpointCount);
	memset(cx->breakpointFlags, 0, breakpointCount);
	cx->breakpointCount = breakpointCount;
	cx->breakpointLines = breakpointLines;
	cx->breakpointScripts = breakpointScripts;
	
	return cx;
}

#ifdef MACOSX
static NSMutableArray *captureStack(jscontext *cx) {
	if (!cx->topOfStack)
		return nil;
	NSMutableArray *result = [[NSMutableArray new] autorelease];
	for (jsframe *f = cx->topOfStack; f; f = f->previousFrame) {
		if (f->source != 0) {
			NSMutableDictionary *item = [[NSMutableDictionary new] autorelease];
			jsstring *strsrc = JSVAL_TO_STRING(f->source);
			[item setValue: [NSNumber numberWithInt:f->line] forKey: @"line"];
			[item setValue: [NSString stringWithCharacters:strsrc->chars length:strsrc->length] forKey: @"script"];
			
			if (f->frame != 0) {
				NSMutableArray *varNames = [[NSMutableArray new] autorelease];
				NSMutableArray *varValues = [[NSMutableArray new] autorelease];
				
				[item setValue: varNames forKey: @"varNames"];
				[item setValue: varValues forKey: @"varValues"];
				
				jsstring *framedefn = JSVAL_TO_STRING(f->frame);
				if (framedefn->length > 0) {
					NSString *desc = [NSString stringWithCharacters:framedefn->chars length:framedefn->length];
					NSArray *varItems = [desc componentsSeparatedByString: @","];
					for (int idx = 0; idx < [varItems count]; idx++) {
						NSString *var = [varItems objectAtIndex:idx];
						NSRange range = [var rangeOfString: @"="];
						NSString *name = [var substringToIndex:range.location];
						int slot = [[var substringFromIndex:range.location + 1] intValue];
						
						[varNames addObject:name];
						
						jsvalue slotval = f->slots[slot];
						[varValues addObject:jsasstrRetain(cx, slotval)];
					}
				}
			}
			
			
			[result addObject:item];
		}
	}
	if ([result count] > 0)
		return result;
	return nil;
}
#endif

void trace(jscontext *cx) {
#ifdef MACOSX
	if (!cx->topOfStack->source)
		return;

	debugger(captureStack(cx), cx->topOfStack->source, cx->topOfStack->line);
	stepDepth = cx->currentDepth;
#endif
}

#ifdef MACOSX
static BOOL shouldTrace(jscontext *cx) {
	if (stepMode == 2) {
		if (cx->currentDepth <= stepDepth)
			return YES;
	}
	return NO;
}

void jsDebugCheckBreakpoint(jscontext *cx, int breakpointIdx, int line) {
	cx->topOfStack->line = line;
	if (stepMode == 1 || cx->breakpointFlags[breakpointIdx] || (stepMode != 0 && shouldTrace(cx)))
		trace(cx);
}

static void setBreakpoint(jscontext *cx, const char *script, int nLine) {
	for (int idx = 0; idx < cx->breakpointCount; idx++) {
		if (!strcmp(cx->breakpointScripts[idx], script)) {
			if (cx->breakpointLines[idx] >= nLine) {
				cx->breakpointFlags[idx] = 1;
				return;
			}
		}
	}
}

static void setScriptBreakpoints(jscontext *cx, NSString *script, NSArray *breakpoints) {
	const char *strScript = [script UTF8String];
	int count = [breakpoints count];
	for (int idx = 0; idx < count; idx++) {
		int nLine = [[breakpoints objectAtIndex:idx] intValue];
		setBreakpoint(cx, strScript, nLine);
	}
}

void resetBreakpoints(jscontext *cx, NSDictionary *breakpoints) {
	memset(cx->breakpointFlags, 0, cx->breakpointCount);
	NSEnumerator * en = [breakpoints keyEnumerator];
	NSString *script;
	while (script = [en nextObject]) {
		NSArray *breakLines = [breakpoints objectForKey:script];
		setScriptBreakpoints(cx, script, breakLines);
	}
}
#endif

void initRuntime(jscontext *cx) {
	cx->maxobjcount = 10;
	cx->mallocBuffer = malloc(8192);
	cx->mallocBufferLen = 8192;
	
	jsvalue Math, Array, Object, String, Date;

	cx->slots[6] = Array = bindFunction(cx, arrayConstructor);
	cx->slots[8] = Object = bindFunction(cx, objectConstructor);
	cx->slots[5] = String = bindFunction(cx, stringConstructor);
	cx->slots[0] = bindFunction(cx, functionConstructor);
	cx->slots[7] = Date = bindFunction(cx, dateConstructor);
	((jsfunction *) Date)->destructor = dateDestructor;
		
	cx->slots[4] = Math = jsnew(cx, Object, NULL, 0);
	
	cx->slots[1] = bindFunctionHint(cx, runtimeDebugstr, "debugstr");
	cx->slots[2] = bindFunctionHint(cx, runtimeDebugstr, "println");
	cx->slots[3] = bindFunctionHint(cx, parseInt, "parseInt");
	
	// init Math object
	jsset(cx, Math, 0, "sqrt", bindFunctionHint(cx, jssqrt, "sqrt"));
	jsset(cx, Math, 0, "abs", bindFunctionHint(cx, jsabs, "abs"));
	jsset(cx, Math, 0, "cos", bindFunctionHint(cx, jscos, "cos"));
	jsset(cx, Math, 0, "floor", bindFunctionHint(cx, mathFloor, "floor"));
	jsset(cx, Math, 0, "random", bindFunctionHint(cx, mathRandom, "random"));
	jsset(cx, Math, 0, "sin", bindFunctionHint(cx, jssin, "sin"));
	jsset(cx, Math, 0, "round", bindFunctionHint(cx, jsround, "round"));
	jsset(cx, Math, 0, "PI", jsnum(cx, 3.14159265358979));
	
	// init date object
	jsaddfield(cx, Date, 0, "getTime", bindFunction(cx, dateGetTime));
#ifdef MACOSX
	jsaddfield(cx, Date, 0, "getMilliseconds", bindFunction(cx, dateGetMilliseconds));
	jsaddfield(cx, Date, 0, "getSeconds", bindFunction(cx, dateGetSeconds));
	jsaddfield(cx, Date, 0, "getHours", bindFunction(cx, dateGetHours));
	jsaddfield(cx, Date, 0, "getMinutes", bindFunction(cx, dateGetMinutes));
#endif
	// init array object
	jsaddfield(cx, Array, 0, "push", bindFunction(cx, arrayPush));
	jsaddfield(cx, Array, 0, "splice", bindFunction(cx, arraySplice));
	jsaddfield(cx, Array, 0, "clear", bindFunction(cx, arrayClear));
	jsadddynamicfield(cx, Array, 0, "length", arrayLength, nil);
	
	// init Function class
	jsaddfield(cx, cx->slots[0], 0, "call", bindFunction(cx, functionCallMethod));
	
	// init String class
	jsaddfield(cx, String, 0, "indexOf", bindFunction(cx, stringIndexOf));
	jsaddfield(cx, String, 0, "lastIndexOf", bindFunction(cx, stringLastIndexOf));
	jsaddfield(cx, String, 0, "substring", bindFunction(cx, stringSubstring));
	jsaddfield(cx, String, 0, "length", bindFunction(cx, stringLength));
	jsaddfield(cx, String, 0, "charAt", bindFunction(cx, stringCharAt));
	
	cx->slots[9] = jsnewobject(cx);
	cx->slots[13] = jsnewobject(cx);
	
	jssetdynamic(cx, cx->slots[13], 0, "appVersion", appVersion, nil);
#ifndef CONSOLE
	registerForm(cx);
	registerControls(cx);
	registerTimer(cx);
#endif
#ifdef MACOSX
	registerObjC(cx);
	registerFile(cx);
	registerTask(cx);
	registerGraphics(cx);
	registerUrl(cx);
	registerXML(cx);
#endif
	//	registerJavaBridge(cx);

#ifdef MACOSX
	if (cx->breakpointLines)
	{
		connectDebugServer(cx);
		if (gInitBreakpoints)
			resetBreakpoints(cx, gInitBreakpoints);
	}
#endif
}

void debug(const char *name, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		printf("%s=%g\n", name, JSVAL_TO_DOUBLE(v)->v);
	} else {
		printf("%s=???\n", name);
	}
}

jsvalue jsnewarray(jscontext *cx) {
	struct jsarray *arr;
	
	if (arr = cx->freeArray) {
		cx->freeArray = (jsarray *) arr->base.nextObject;
	} else {
		arr = fmalloc(cx, sizeof(struct jsarray));
	}
	
	initObject(cx, &arr->base, (jsfunction *) (cx->slots[6]), "array");
	arr->base.flags |= FLAG_ARRAY;
	arr->count = 0;
	arr->content = NULL;
	return (jsvalue) arr;
}

void jspush(jscontext *cx, jsvalue array, jsvalue v) {
	struct jsarray *arr = (struct jsarray *) array;
	arr->count++;
	arr->content = realloc(arr->content, sizeof(jsvalue) * arr->count);
	arr->content[arr->count - 1] = v;
}

void unimplemented() {
	printf("unimplemented\n");
	exit(1);
}

jsbool isFunction(jsvalue v) {
	if (v != 0 && JSVAL_IS_OBJECT(v)) {
		jsobject *fn = JSVAL_TO_OBJECT(v);
		return 0 != (fn->flags & FLAG_FUNCTION);
	} else
		return 0;
}

jsbool isArray(jsvalue v) {
	if (v != 0 && JSVAL_IS_OBJECT(v)) {
		jsobject *arr = JSVAL_TO_OBJECT(v);
		return 0 != (arr->flags & FLAG_ARRAY);
	} else
		return 0;
}

int jsarraylength(jsvalue v) {
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(v);
	return arr->count;
}

jsvalue jsarrayelem(jsvalue v, int idx) {
	jsarray *arr = (jsarray *) JSVAL_TO_OBJECT(v);
	if (idx >= 0 && idx < arr->count)
		return arr->content[idx];
	return JSVAL_VOID;
}

jsbool isFunctionObj(jsobject *v) {
	if (v != 0) {
		return 0 != (v->flags & FLAG_FUNCTION);
	} else
		return 0;
}

jsbool isPrimitive(jscontext *cx, jsvalue v) {
	if (v != 0 && JSVAL_IS_OBJECT(v)) {
		return 0;
	}
	return 1;
}

jsvalue toValueNumber(jscontext *cx, jsvalue v) {
	if (v != 0 && JSVAL_IS_OBJECT(v)) {
		jsvalue cmp = jsget(cx, v, 0, "valueOf");
		if (isFunction(cmp)) {
			jsvalue cvt = jsinvoke(cx, cmp, v, 0, 0);
			if (isPrimitive(cx, cvt))
				return cvt;
		}
		cmp = jsget(cx, v, 0, "toString");
		if (isFunction(cmp)) {
			jsvalue cvt = jsinvoke(cx, cmp, v, 0, 0);
			if (isPrimitive(cx, cvt))
				return cvt;
		}
		return JSVAL_VOID;
	} else {
		return v;
	}
}

int jslessthan(jscontext *cx, jsvalue v, jsvalue v2) {
	v = toValueNumber(cx, v);
	v2 = toValueNumber(cx, v2);
	
	if (!JSVAL_IS_STRING(v) || !JSVAL_IS_STRING(v2)) {
		double vn = jsasnum(cx, v);
		double v2n = jsasnum(cx, v2);
		if (isnan(vn) || isnan(v2n))
			return 2;
		if (vn == v2n)
			return 0;
		if (vn == -0.0 && v2n == 0.0)
			return 0;
		if (vn == 0.0 && v2n == -0.0)
			return 0;
		if (vn < v2n)
			return 1;
		return 0;
	}
	// both are strings
	int cmp = compareStrings(JSVAL_TO_STRING(v), JSVAL_TO_STRING(v2));
//	int cmp = [JSVAL_TO_STRING(v)->str compare:JSVAL_TO_STRING(v2)->str];
	if (cmp < 0)
		return 1;
	return 0;
}

jsvalue jsle(jscontext *cx, jsvalue v, jsvalue v2) {
	int cmp = jslessthan(cx, v2, v);
	if (cmp == 0)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jslt(jscontext *cx, jsvalue v, jsvalue v2) {
	int cmp = jslessthan(cx, v, v2);
	if (cmp == 1)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsge(jscontext *cx, jsvalue v, jsvalue v2) {
	int cmp = jslessthan(cx, v, v2);
	if (cmp == 0)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsgt(jscontext *cx, jsvalue v, jsvalue v2) {
	int cmp = jslessthan(cx, v2, v);
	if (cmp == 1)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jseq(jscontext *cx, jsvalue v, jsvalue v2) {
	if (v == JSVAL_VOID && v2 == JSVAL_VOID)
		return JSVAL_TRUE;
	if (v == JSVAL_NULL && v2 == JSVAL_NULL)
		return JSVAL_TRUE;
	if (v == JSVAL_VOID && v2 == JSVAL_NULL)
		return JSVAL_TRUE;
	if (v == JSVAL_NULL && v2 == JSVAL_VOID)
		return JSVAL_TRUE;
	if (v == JSVAL_NULL || v2 == JSVAL_NULL)
		return JSVAL_FALSE;
	if (v == JSVAL_VOID || v2 == JSVAL_VOID)
		return JSVAL_FALSE;

	if (JSVAL_IS_BOOLEAN(v) && JSVAL_IS_BOOLEAN(v2)) {
		return v == v2 ? JSVAL_TRUE : JSVAL_FALSE;
	}
	if (JSVAL_IS_OBJECT(v) && JSVAL_IS_OBJECT(v2))
		return v == v2 ? JSVAL_TRUE : JSVAL_FALSE;
	
	if (JSVAL_IS_STRING(v) && JSVAL_IS_STRING(v2)) {
		return BOOLEAN_TO_JSVAL(equalStrings(JSVAL_TO_STRING(v), JSVAL_TO_STRING(v2)));
	}
	if (jsisnum(v) && jsisnum(v2)) {
		double nv = jsasnum(cx, v);
		double nv2 = jsasnum(cx, v2);
		
		if (isnan(nv) || isnan(nv2))
			return JSVAL_TRUE;
		return nv == nv2 ? JSVAL_TRUE : JSVAL_FALSE;
	}
	jserror(cx, "Type mismatch");
	return JSVAL_FALSE;
}

jsvalue jseqeq(jscontext *cx, jsvalue v, jsvalue v2) {
	if (v == JSVAL_VOID && v2 == JSVAL_VOID)
		return JSVAL_TRUE;
	if (v == JSVAL_NULL && v2 == JSVAL_NULL)
		return JSVAL_TRUE;
	if (v == JSVAL_NULL || v2 == JSVAL_NULL)
		return JSVAL_FALSE;
	if (v == JSVAL_VOID || v2 == JSVAL_VOID)
		return JSVAL_FALSE;
	
	if (JSVAL_IS_BOOLEAN(v) && JSVAL_IS_BOOLEAN(v2)) {
		return v == v2 ? JSVAL_TRUE : JSVAL_FALSE;
	}
	if (JSVAL_IS_OBJECT(v) && JSVAL_IS_OBJECT(v2))
		return v == v2 ? JSVAL_TRUE : JSVAL_FALSE;
	
	if (JSVAL_IS_STRING(v) && JSVAL_IS_STRING(v2)) {
		return BOOLEAN_TO_JSVAL(equalStrings(JSVAL_TO_STRING(v), JSVAL_TO_STRING(v2)));
	}
	if (jsisnum(v) && jsisnum(v2)) {
		double nv = jsasnum(cx, v);
		double nv2 = jsasnum(cx, v2);
		
		if (isnan(nv) || isnan(nv2))
			return JSVAL_TRUE;
		return nv == nv2 ? JSVAL_TRUE : JSVAL_FALSE;
	}
	return JSVAL_FALSE;
}

jsvalue jsne(jscontext *cx, jsvalue v, jsvalue v2) {
	jsvalue eq = jseq(cx, v, v2);
	if (eq == JSVAL_FALSE)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsnene(jscontext *cx, jsvalue v, jsvalue v2) {
	jsvalue eq = jseqeq(cx, v, v2);
	if (eq == JSVAL_FALSE)
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsnot(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		if (JSVAL_TO_DOUBLE(v)->v)
			return JSVAL_FALSE;
		else
			return JSVAL_TRUE;
	}
	if (JSVAL_IS_INT(v)) {
		if (JSVAL_TO_INT(v))
			return JSVAL_FALSE;
		else
			return JSVAL_TRUE;
	}
	if (JSVAL_IS_BOOLEAN(v)) {
		if (JSVAL_TO_BOOLEAN(v))
			return JSVAL_FALSE;
		else
			return JSVAL_TRUE;
	}
	if (v == JSVAL_VOID || v == JSVAL_NULL)
		return JSVAL_TRUE;
	
	jserror(cx, "Type mismatch");
	return JSVAL_FALSE;
}

#define MIN_INT -1073741823
#define MAX_INT 1073741823

jsvalue jsneg(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		return jsnum(cx, -JSVAL_TO_DOUBLE(v)->v);
	}
	if (JSVAL_IS_INT(v)) {
		int iv = -JSVAL_TO_INT(v);
		if (iv >= MIN_INT && iv <= MAX_INT)
			return INT_TO_JSVAL(iv);
		return jsnum(cx, iv);
	}
	
	jserror(cx, "Type mismatch");
	return JSVAL_NULL;
}

jsvalue jsinc(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		double dv = JSVAL_TO_DOUBLE(v)->v + 1.0;
		if (dv >= MIN_INT && dv <= MAX_INT && dv == floor(dv))
			return jsnumi(cx, (int) dv);
		return jsnum(cx, dv);
	} else if (JSVAL_IS_INT(v)) {
		return jsnumi(cx, JSVAL_TO_INT(v) + 1);
	} else
		jserror(cx, "increment can only be used on number values");
	return JSVAL_VOID;
}

jsvalue jsdec(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_DOUBLE(v)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v - 1.0);
	} else if (JSVAL_IS_INT(v)) {
		return jsnum(cx, JSVAL_TO_INT(v) - 1.0);
	} else
		jserror(cx, "decrement cab only be used on number values");
	return JSVAL_VOID;
}

jsvalue jsand(jscontext *cx, jsvalue v, jsvalue v2) {
	if (jsasbool(cx, v) && jsasbool(cx, v2))
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsor(jscontext *cx, jsvalue v, jsvalue v2) {
	if (jsasbool(cx, v) || jsasbool(cx, v2))
		return JSVAL_TRUE;
	else
		return JSVAL_FALSE;
}

jsvalue jsadd(jscontext *cx, jsvalue v, jsvalue v2) {
	if (JSVAL_IS_INT(v) && JSVAL_IS_INT(v2)) {
		return jsnumi(cx, JSVAL_TO_INT(v) + JSVAL_TO_INT(v2));
	} else if (jsisnum(v) && jsisnum(v2)) {
		return jsnum(cx, jsasnum(cx, v) + jsasnum(cx, v2));
	} else {
		if (cx->objcount > cx->maxobjcount) {
			jsgc(cx);
		}
		jsstring *a = jsasstr(cx, v);
		jsstring *b = jsasstr(cx, v2);
		
		unsigned short *str = malloc(sizeof(unsigned short) * (a->length + b->length));
		memcpy(str, a->chars, sizeof(unsigned short) * a->length);
		memcpy(str + a->length, b->chars, sizeof(unsigned short) * b->length);
		
		return jsstrFromChars(cx, str, a->length + b->length);
	}
}

jsvalue jslsh(jscontext *cx, jsvalue v, jsvalue v2) {
	return jsnumi(cx, jsasint(cx, v) << jsasint(cx, v2));
}

jsvalue jsrsh(jscontext *cx, jsvalue v, jsvalue v2) {
	return jsnumi(cx, jsasint(cx, v) >> jsasint(cx, v2));
}

jsvalue jsursh(jscontext *cx, jsvalue v, jsvalue v2) {
	return jsnumi(cx, jsasint(cx, v) >> jsasint(cx, v2));
}

jsvalue jssub(jscontext *cx, jsvalue v, jsvalue v2) {
	if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v - JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_INT(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v - JSVAL_TO_INT(v2));
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_INT(v) - JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_INT(v2)) {
		return jsnumi(cx, JSVAL_TO_INT(v) - JSVAL_TO_INT(v2));
	} else {
		jserror(cx, "Type mismatch");
	}
	return JSVAL_VOID;
}

jsvalue jsdiv(jscontext *cx, jsvalue v, jsvalue v2) {
	if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v / JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_INT(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v / JSVAL_TO_INT(v2));
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_INT(v) / JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_INT(v2)) {
		double dv = (double) JSVAL_TO_INT(v) / (double) JSVAL_TO_INT(v2);
		if (dv >= MIN_INT && dv <= MAX_INT && dv == floor(dv))
			return jsnumi(cx, dv);
		else
			return jsnum(cx, dv);
	} else {
		jserror(cx, "Type mismatch");
	}
	return JSVAL_VOID;
}

jsvalue jsmul(jscontext *cx, jsvalue v, jsvalue v2) {
	if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v * JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_DOUBLE(v) && JSVAL_IS_INT(v2)) {
		return jsnum(cx, JSVAL_TO_DOUBLE(v)->v * JSVAL_TO_INT(v2));
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_DOUBLE(v2)) {
		return jsnum(cx, JSVAL_TO_INT(v) * JSVAL_TO_DOUBLE(v2)->v);
	} else if (JSVAL_IS_INT(v) && JSVAL_IS_INT(v2)) {
		double dv = (double) JSVAL_TO_INT(v) * (double) JSVAL_TO_INT(v2);
		if (dv >= MIN_INT && dv <= MAX_INT)
			return jsnumi(cx, (int) dv);
		return jsnum(cx, dv);
	} else {
		jserror(cx, "Type mismatch");
	}
	return JSVAL_VOID;
}

jsvalue jsnumi(jscontext *cx, int v) {
	if (v >= MIN_INT && v <= MAX_INT)
		return INT_TO_JSVAL(v);
	struct jsdouble *dbl;
	
	cx->objcount++;
	
	if (dbl = cx->freeDouble) {
		cx->freeDouble = dbl->nextDouble;
	} else {
		dbl = (struct jsdouble *) fmalloc(cx, sizeof(struct jsdouble));
	}
	dbl->flags = 0;
	dbl->v = v;
	dbl->prevDouble = NULL;
	dbl->nextDouble = cx->firstDouble;
	
	if (cx->firstDouble)
		cx->firstDouble->prevDouble = dbl;
	cx->firstDouble = dbl;
	return DOUBLE_TO_JSVAL(dbl);
}

jsvalue jsnum(jscontext *cx, double v) {
	if (cx->objcount > cx->maxobjcount)
		jsgc(cx);
	
	cx->objcount++;
	
	jsdouble *dbl;
	
	if (dbl = cx->freeDouble) {
		cx->freeDouble = dbl->nextDouble;
	} else {
		dbl = (struct jsdouble *) fmalloc(cx, sizeof(struct jsdouble));
	}
	dbl->v = v;
	dbl->flags = 0;
	dbl->prevDouble = NULL;
	dbl->nextDouble = cx->firstDouble;
	
	if (cx->firstDouble)
		cx->firstDouble->prevDouble = dbl;
	cx->firstDouble = dbl;
	return DOUBLE_TO_JSVAL(dbl);
}

void jssetslot(jscontext *cx, jsvalue v, int h, const char *elem, jsvalue v2) {
	if (JSVAL_IS_OBJECT(v)) {
		struct jsobject *obj = JSVAL_TO_OBJECT(v);
		struct jsfield *f;
					
		for (f = obj->fields; f; f = f->next) {
			if ((f->hash == h || f->hash == 0 || h == 0) && equalStringsJC(f->field, elem)) {
				if (f->setter) {
					jsinvoke(cx, f->setter, v, &v2, 1);
					return;
				} else {
					f->val = v2;
					return;
				}
			}
		}
		
		obj = JSVAL_TO_OBJECT(v);
		if (f = cx->freeField)
			cx->freeField = f->next;
		else
			f = fmalloc(cx, sizeof(struct jsfield));
		f->field = jsstrFromCstring(cx, elem);
		f->hash = h;
		f->val = v2;
		f->getter = 0;
		f->setter = 0;
		f->next = obj->fields;
		obj->fields = f;
	}
}

void jssett(jscontext *cx, jsvalue v, int h, jsvalue elem, jsvalue v2) {
	if (JSVAL_IS_OBJECT(v)) {
		struct jsobject *obj = JSVAL_TO_OBJECT(v);
		struct jsfield *f;
		
		if (isFunctionObj(obj) && equalStringsJC(JSVAL_TO_STRING(elem), "prototype")) {
			jsfunction *fn = (jsfunction *) obj;
			if (JSVAL_IS_OBJECT(v2) && v2 != 0)
				fn->prototype = JSVAL_TO_OBJECT(v2);
			return;
		}
		
		if (obj->cls != NULL && obj->cls->customSet != nil) {
			obj->cls->customSet(cx, v, h, elem, v2);
			return;
		}
		
		
		jsbool primary = true;
		while (obj != nil) {
			jsobject *next = nil;
			
			for (f = obj->fields; f; f = f->next) {
				if ((f->hash == h || f->hash == 0 || h == 0) && equalStrings(JSVAL_TO_STRING(elem), JSVAL_TO_STRING(f->field))) {
					if (f->setter) {
						jsinvoke(cx, f->setter, v, &v2, 1);
						return;
					} else if (primary) {
						f->val = v2;
						return;
					}
				}
			}
			
			primary = false;
			// walk the prototype chain
			if (obj->cls != NULL) {
				next = obj->cls->prototype;
			}
			obj = next;
		}
		
		obj = JSVAL_TO_OBJECT(v);
		if (f = cx->freeField)
			cx->freeField = f->next;
		else
			f = fmalloc(cx, sizeof(struct jsfield));
		f->field = elem;
		f->hash = h;
		f->val = v2;
		f->getter = 0;
		f->setter = 0;
		f->next = obj->fields;
		obj->fields = f;
	}
}

void jsset(jscontext *cx, jsvalue v, int h, const char *elem, jsvalue v2) {
	jssett(cx, v, h, jsstrFromCstring(cx, elem), v2);
}

void jscopy(jscontext *cx, jsvalue destObj, jsvalue srcObj, int h, const char *fieldName) {
	jsvalue v = jsget(cx, srcObj, h, fieldName);
	if (v != JSVAL_VOID) {
		jsset(cx, destObj, h, fieldName, v);
	}
}

void jssetdynamic(jscontext *cx, jsvalue v, int h, const char *fieldName, jsvalue (*getter)(jscontext *, jsframe *, jsvalue, jsvalue *, int), jsvalue (*setter)(jscontext *, jsframe *, jsvalue, jsvalue *, int)) {
	jsvalue getterfn = bindFunction(cx, getter);
	jsvalue setterfn = bindFunction(cx, setter);
	
	jsobject *obj = JSVAL_TO_OBJECT(v);
	
	jsfield *fld;
	if (fld = cx->freeField) {
		cx->freeField = fld->next;
	} else {
		fld = fmalloc(cx, sizeof(jsfield));
	}
	fld->field = jsstrFromCstring(cx, fieldName);
	fld->hash = h;
	fld->getter = getterfn;
	fld->setter = setterfn;
	fld->val = JSVAL_VOID;
	fld->next = obj->fields;
	obj->fields = fld;
}

void jsdelete(jscontext *cx, jsvalue v, int h, jsvalue elem) {
	if (v && JSVAL_IS_OBJECT(v)) {
		jsobject *obj = JSVAL_TO_OBJECT(v);
		jsfield *last = nil;
		for (jsfield *f = obj->fields; f; f = f->next) {
			if ((f->hash == h || f->hash == 0 || h == 0) && equalStrings(JSVAL_TO_STRING(elem), JSVAL_TO_STRING(f->field))) {
				if (last)
					last->next = f->next;
				else
					obj->fields = f->next;
				
				f->next = cx->freeField;
				cx->freeField = f;
				return;
			}
			last = f;
		}
	}
}

jsvalue jsgetslot(jscontext *cx, jsvalue v, int h, const char *elem) {
	if (v && JSVAL_IS_OBJECT(v)) {
		struct jsobject *obj = JSVAL_TO_OBJECT(v);
		
		struct jsfield *f;
		for (f = obj->fields; f; f = f->next) {
			if ((f->hash == h || f->hash == 0 || h == 0) && equalStringsJC(JSVAL_TO_STRING(f->field), elem)) {
				if (f->getter)
					return jsinvoke(cx, f->getter, v, nil, 0);
				return f->val;
			}
		}
	}
	return JSVAL_VOID;
}

jsvalue jsgett(jscontext *cx, jsvalue v, int h, jsvalue elem) {
	if (JSVAL_IS_STRING(v)) {
		return jsgett(cx, (jsvalue) (((jsfunction *) cx->slots[5])->prototype), h, elem);
	}
	
	if (v && JSVAL_IS_OBJECT(v)) {
		struct jsobject *obj = JSVAL_TO_OBJECT(v);
		
		if (obj->flags & FLAG_DELETED)
			jserror(cx, "accessing deleted object");
		
		// look for custom getter
		if (obj->cls != NULL) {
			if (obj->cls->customGet) {
				return obj->cls->customGet(cx, v, h, elem);
			}
		}
		
		/*
		if (obj->cls == (jsfunction *) (cx->Array) && !strcmp(elem, "length")) {
			jsarray *arr = (jsarray *) obj;
			return INT_TO_JSVAL(arr->count);
		}
		 */
		if (isFunctionObj(obj) && equalStringsJC(JSVAL_TO_STRING(elem), "prototype")) {
			jsfunction *fn = (jsfunction *) obj;
			return OBJECT_TO_JSVAL(fn->prototype);
		}
		
		while (obj != nil) {
			jsobject *next = nil;
			struct jsfield *f;
			for (f = obj->fields; f; f = f->next) {
				if ((f->hash == h || f->hash == 0 || h == 0) && equalStrings(JSVAL_TO_STRING(elem), JSVAL_TO_STRING(f->field))) {
					if (f->getter)
						return jsinvoke(cx, f->getter, v, nil, 0);
					return f->val;
				}
			}
			
			// walk the prototype chain
			if (obj->cls != NULL) {
				next = obj->cls->prototype;
			}
			obj = next;
		}
	}
	return JSVAL_VOID;
}

jsvalue jsgetattrt(jscontext *cx, jsvalue v, int h, jsvalue elem) {
	if (JSVAL_IS_OBJECT(v) && v != JSVAL_VOID) {
		struct jsobject *obj = JSVAL_TO_OBJECT(v);
		if (obj->cls != nil) {
			if (obj->cls->customGetAttr != nil)
				return obj->cls->customGetAttr(cx, v, h, elem);
		}
	}
	
	return JSVAL_VOID;
}

jsvalue jsgetdesct(jscontext *cx, jsvalue v, int h, jsvalue elem) {
	unimplemented();
	return 0;
}

jsvalue jsget(jscontext *cx, jsvalue v, int h, const char *elem) {
	return jsgett(cx, v, h, jsstrFromCstring(cx, elem));
}

int jsgetint(jscontext *cx, jsvalue obj, int h,  const char *elem, int defaultValue) {
	jsvalue v = jsget(cx, obj, h, elem);
	if (v == JSVAL_VOID)
		return defaultValue;
	jsaddroot(cx, &v);
	int dv = jsasint(cx, v);
	jsremoveroot(cx, &v);
	return dv;
}

jsbool jsgetbool(jscontext *cx, jsvalue obj, int h,  const char *elem, jsbool defaultValue) {
	jsvalue v = jsget(cx, obj, h, elem);
	if (v == JSVAL_VOID)
		return defaultValue;
	jsaddroot(cx, &v);
	jsbool dv = jsasbool(cx, v);
	jsremoveroot(cx, &v);
	return dv;
}

double jsgetdouble(jscontext *cx, jsvalue obj, int h,  const char *elem, double defaultValue) {
	jsvalue v = jsget(cx, obj, h, elem);
	if (v == JSVAL_VOID)
		return defaultValue;
	jsaddroot(cx, &v);
	double dv = jsasnum(cx, v);
	jsremoveroot(cx, &v);
	return dv;
}

#ifdef MACOSX
NSString *jsgetstring(jscontext *cx, jsvalue obj, int h,  const char *elem, NSString *defaultValue) {
	jsvalue v = jsget(cx, obj, h, elem);
	if (v == JSVAL_VOID)
		return defaultValue;
	jsaddroot(cx, &v);
	NSString *dv = jsasstrRetain(cx, v);
	jsremoveroot(cx, &v);
	[dv autorelease];
	return dv;
}


#endif

jsvalue jsgetelem(jscontext *cx, jsvalue v, jsvalue elem) {
	if (!v)
		jserror(cx, "undefined value");
	if (v == JSVAL_NULL)
		jserror(cx, "null value");
	if (!JSVAL_IS_OBJECT(v))
		return JSVAL_VOID;
	
	if (!isArray(v)) {
		if (v && JSVAL_IS_OBJECT(v)) {
			struct jsobject *obj = JSVAL_TO_OBJECT(v);
			
			// look for custom getter
			if (obj->cls != NULL) {
				if (obj->cls->customGet) {
					return obj->cls->customGet(cx, v, 0, elem);
				}
			}
		}
		
		jsstring *key = jsasstr(cx, elem);
		return jsgett(cx, v, 0, STRING_TO_JSVAL(key));
	}
	
	jsarray *arr = (jsarray *) v;
	
	int idx = 0;
	jsbool hasIdx = true;
	if (JSVAL_IS_INT(elem))
		idx = JSVAL_TO_INT(elem);
	else if (JSVAL_IS_DOUBLE(elem))
		idx = JSVAL_TO_DOUBLE(elem)->v;
	else
		hasIdx = false;
	
	if (idx >= 0 && idx < arr->count)
		return arr->content[idx];
	
	unimplemented();
	return JSVAL_VOID;
}

void jssetelem(jscontext *cx, jsvalue v, jsvalue elem, jsvalue v2) {
	if (!v)
		jserror(cx, "undefined value");
	if (v == JSVAL_NULL)
		jserror(cx, "null value");
	if (!JSVAL_IS_OBJECT(v))
		jserror(cx, "not an object");
	
	if (!isArray(v)) {
		jsstring *key = jsasstr(cx, elem);
		jssett(cx, v, 0, STRING_TO_JSVAL(key), v2);
		return;
	}
	
	jsarray *arr = (jsarray *) v;
	
	int idx;
	if (JSVAL_IS_INT(elem))
		idx = JSVAL_TO_INT(elem);
	else if (JSVAL_IS_DOUBLE(elem))
		idx = JSVAL_TO_DOUBLE(elem)->v;
	else
	{
#ifdef MACOSX
		NSString *key = jsasstrRetain(cx, elem);
		jsset(cx, v, 0, keyFromString(key), v2);
		[key release];
		return;
#else
		unimplemented();
#endif
	}
	
	if (idx < 0)
		unimplemented();
	
	if (idx < arr->count) {
		arr->content[idx] = v2;
		return;
	}
	
	arr->content = realloc(arr->content, sizeof(jsvalue) * (idx + 1));
	for (int i = arr->count; i < idx; i++) {
		arr->content[i] = JSVAL_VOID;
	}
	arr->content[idx] = v2;
	arr->count = idx + 1;
}


int ifeq(jsvalue v) {
	if (JSVAL_IS_BOOLEAN(v)) {
		return JSVAL_TO_BOOLEAN(v);
	} else if (JSVAL_IS_INT(v)) {
		return JSVAL_TO_INT(v) != 0;
	} else if (JSVAL_IS_DOUBLE(v)) {
		return JSVAL_TO_DOUBLE(v)->v != 0;
	} else if (v == JSVAL_VOID || v == JSVAL_NULL) {
		return 0;
	} else {
		return 1;
	}
}

int ifne(jsvalue v) {
	return !ifeq(v);
}

jsvalue bindFunctionHint(jscontext *cx, jsvalue (*proc)(jscontext *, jsframe *, jsvalue, jsvalue *, int), const char *hint) {
	jsfunction *fn;
	
	if (fn = cx->freeFunction)
		cx->freeFunction = (jsfunction *) fn->base.nextObject;
	else
		fn = fmalloc(cx, sizeof(jsfunction));
	initObject(cx, &fn->base, (jsfunction *) cx->slots[0], hint);
	fn->base.flags = FLAG_CONSTANT | FLAG_FUNCTION;
	fn->proc = proc;
	fn->destructor = nil;
	fn->customGet = nil;
	fn->customSet = nil;
	fn->customInvoke = nil;
	fn->outer = nil;
	fn->prototype = newObject(cx);
	return (jsvalue)(fn);
}

jsvalue bindFunction(jscontext *cx, jsvalue (*proc)(jscontext *, jsframe *, jsvalue, jsvalue *, int)) {
	jsfunction *fn;
	
	if (fn = cx->freeFunction)
		cx->freeFunction = (jsfunction *) fn->base.nextObject;
	else
		fn = fmalloc(cx, sizeof(jsfunction));
	initObject(cx, &fn->base, (jsfunction *) (cx->slots[0]), "boundFunction");
	fn->base.flags |= FLAG_CONSTANT | FLAG_FUNCTION;
	fn->proc = proc;
	fn->outer = nil;
	fn->customGet = nil;
	fn->customSet = nil;
	fn->customInvoke = nil;
	fn->destructor = nil;
	fn->prototype = newObject(cx);
	return (jsvalue)(fn);
}

jsvalue jsclass(jscontext *cx, jsvalue (*constructor)(jscontext *, jsframe *, jsvalue, jsvalue *, int), jsvalue superClass) {
	jsvalue cls = bindFunction(cx, constructor);
	jsfunction *fn = (jsfunction *) cls;
	fn->prototype = JSVAL_TO_OBJECT(jsnew(cx, superClass, nil, 0));
	return cls;
}

void jsadddynamicfield(jscontext *cx, jsvalue cls, int h, const char *fieldName, jsvalue (*getter)(jscontext *, jsframe *, jsvalue, jsvalue *, int), jsvalue (*setter)(jscontext *, jsframe *, jsvalue, jsvalue *, int)) {
	jsvalue getterfn = bindFunction(cx, getter);
	jsvalue setterfn = bindFunction(cx, setter);
	
	jsfunction *fn = (jsfunction *) cls;
	
	jsfield *fld;
	if (fld = cx->freeField) {
		cx->freeField = fld->next;
	} else {
		fld = fmalloc(cx, sizeof(jsfield));
	}
	fld->field = jsstrFromCstring(cx, fieldName);
	fld->hash = h;
	fld->getter = getterfn;
	fld->setter = setterfn;
	fld->val = JSVAL_VOID;
	fld->next = fn->prototype->fields;
	fn->prototype->fields = fld;
}

void jsaddfield(jscontext *cx, jsvalue cls, int h, const char *fieldName, jsvalue val) {
	jsfunction *fn = (jsfunction *) cls;
	
	jsfield *fld;
	if (fld = cx->freeField) {
		cx->freeField = fld->next;
	} else {
		fld = fmalloc(cx, sizeof(jsfield));
	}
	fld->field = jsstrFromCstring(cx, fieldName);
	fld->hash = h;
	fld->getter = 0;
	fld->setter = 0;
	fld->val = val;
	fld->next = fn->prototype->fields;
	fn->prototype->fields = fld;
}

jsvalue jsclosure(jscontext *cx, jsvalue (*proc)(jscontext *, jsframe *, jsvalue, jsvalue *, int), jsframe *frame) {
	jsfunction *fn;
	if (cx->objcount > cx->maxobjcount)
		jsgc(cx);
	
	if (fn = cx->freeFunction)
		cx->freeFunction = (jsfunction *) fn->base.nextObject;
	else
		fn = fmalloc(cx, sizeof(jsfunction));
	
	initObject(cx, &fn->base, (jsfunction *)(cx->slots[0]), "function");
	fn->base.flags |= FLAG_FUNCTION;
	fn->proc = proc;
	fn->destructor = nil;
	fn->outer = frame;
	fn->customGet = nil;
	fn->customSet = nil;
	fn->customInvoke = nil;
	fn->prototype = newObject(cx);
	frame->refCount++;
	return (jsvalue)fn;
}

jsvalue jsinvoke(jscontext *cx, jsvalue func, jsvalue thisArg, jsvalue *params, int paramCount) {
	if (func == JSVAL_VOID)
		jserror(cx, "undefined value cannot be invoked");
	if (func == JSVAL_NULL)
		jserror(cx, "null value cannot be invoked");
	if (!JSVAL_IS_OBJECT(func))
		jserror(cx, "value cannot be invoked");
	jsobject *obj = (jsobject *) func;
	if (isFunctionObj(obj)) {
		jsfunction *fn = (jsfunction *) JSVAL_TO_OBJECT(func);
		return fn->proc(cx, fn->outer, thisArg, params, paramCount);
	} else if (obj->cls && obj->cls->customInvoke != nil) {
		return obj->cls->customInvoke(cx, func, thisArg, params, paramCount);
	} else {
		jserror(cx, "value cannot be invoked");
	}
	return JSVAL_VOID;
}

jsvalue jsinvokename(jscontext *cx, jsvalue thisArg, const char *name, jsvalue *params, int paramCount) {
	jsvalue fn = jsget(cx, thisArg, 0, name);
	if (fn && JSVAL_IS_OBJECT(fn)) {
		return jsinvoke(cx, fn, thisArg, params, paramCount);
	}
	return JSVAL_VOID;
}

jsvalue jsnumconst(jscontext *cx, double v) {
	jsdouble *dbl = fmalloc(cx, sizeof(jsdouble));
	dbl->v = v;
	dbl->flags = FLAG_CONSTANT;
	return DOUBLE_TO_JSVAL(dbl);
}

#ifdef MACOSX

jsvalue jsstrFromNSString(jscontext *cx, NSString *strval) {
	jsstring *str;
	
	cx->objcount++;
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	str->length = [strval length];
	str->chars = malloc((str->length + 1) * sizeof(unsigned short));
	[strval getCharacters:str->chars];
	str->nsstr = [strval retain];
	str->flags = 0;
	
	str->prevString = NULL;
	str->nextString = cx->firstString;
	
	if (cx->firstString)
		cx->firstString->prevString = str;
	
	cx->firstString = str;
	
	return STRING_TO_JSVAL(str);
}
#endif

jsvalue jsstrconst(jscontext *cx, void *constStr, int len) {
	jsstring *str;
	
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	
	if (sizeof(wchar_t) == sizeof(unsigned short))
		str->chars = (unsigned short *) constStr;
	else
	{
		str->chars = malloc(sizeof(unsigned short) * len);
		for (int idx = 0; idx < len; idx++)
			str->chars[idx] = ((wchar_t *) constStr)[idx];
	}
	str->length = len;
#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = FLAG_CONSTANT;
	return STRING_TO_JSVAL(str);
}

jsvalue jsstrnconst(jscontext *cx, unsigned short *constStr, int len) {
	jsstring *str;
	
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	
	str->chars = constStr;
	str->length = len;
#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = FLAG_CONSTANT;
	return STRING_TO_JSVAL(str);
}

jsvalue jsstrn(jscontext *cx, unsigned short *strval, int len)
{
	jsstring *str;
	
	cx->objcount++;
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	str->length = len;
	str->chars = malloc(len * sizeof(unsigned short));
	if (len)
		memcpy(str->chars, strval, len * sizeof(unsigned short));

#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = 0;
	
	str->prevString = NULL;
	str->nextString = cx->firstString;
	
	if (cx->firstString)
		cx->firstString->prevString = str;
	
	cx->firstString = str;
	
	return STRING_TO_JSVAL(str);
}

jsobject *newObject(jscontext *cx) {
	struct jsobject *v;
	
	if (v = cx->freeObject) {
		cx->freeObject = v->nextObject;
	} else {
		v  = (struct jsobject *) fmalloc(cx, sizeof(struct jsobject));
	}
	initObject(cx, v, (jsfunction *)(cx->slots[8]), "object");
	return v;
}

jsvalue jsnewobject(jscontext *cx) {
	return OBJECT_TO_JSVAL(newObject(cx));
}

jsvalue jsnew(jscontext *cx, jsvalue cls, jsvalue *params, int paramCount) {
	if (cx->objcount > cx->maxobjcount) {
		jsgc(cx);
	}
	
	if (isFunction(cls)) {
		jsfunction *objectClass = (jsfunction *) cls;
		
		if (cls == cx->slots[6]) { // Array
			struct jsarray *v;
			
			if (v = cx->freeArray)
				cx->freeArray = (jsarray *) v->base.nextObject;
			else
				v = (struct jsarray *) fmalloc(cx, sizeof(struct jsarray));
			initObject(cx, &v->base, (jsfunction *) cls, "jsnew");
			v->base.flags |= FLAG_ARRAY;
			v->count = 0;
			v->content = 0;
			return (jsvalue) v;
		} else {
			struct jsobject *v;
			
			if (v = cx->freeObject) {
				cx->freeObject = v->nextObject;
			} else {
				v  = (struct jsobject *) fmalloc(cx, sizeof(struct jsobject));
			}
			initObject(cx, v, objectClass, "jsnew");
			
			jsaddroot(cx, (jsvalue *) &v);
			jsfunction *fn = (jsfunction *) objectClass;
			fn->proc(cx, fn->outer, OBJECT_TO_JSVAL(v), params, paramCount);
			jsremoveroot(cx, (jsvalue *) &v);
			
			return (jsvalue) v;
		}
	} else
		jserror(cx, "New operator is not applicable to value");
	return JSVAL_VOID;
}

jsframe *enterStack(jscontext *cx, jsframe *outer, int slotCount, int callCount) {
	cx->objcount++;
	jsframe *f = (jsframe *) malloc(sizeof(jsframe));
	f->refCount = 1;
	f->outer = outer;
	f->source = 0;
	f->frame = 0;
	f->line = 0;
	
	if (outer)
		outer->refCount++;
	
	f->slots = malloc(slotCount * sizeof(jsvalue));
	memset(f->slots, 0, slotCount * sizeof(jsvalue));
	f->callFrame = malloc(callCount * sizeof(jsvalue));
	memset(f->callFrame, 0, callCount * sizeof(jsvalue));
	f->slotCount = slotCount;
	f->callFrameCount = callCount;
	f->previousFrame = cx->topOfStack;
	f->stackRoots = nil;
	cx->topOfStack = f;
	cx->currentDepth++;
	return f;
}

jsframe *enterStackDebug(jscontext *cx, jsframe *outer, int slotCount, int callCount, jsvalue source, jsvalue frame) {
	jsframe *f = enterStack(cx, outer, slotCount, callCount);
	f->source = source;
	f->frame = frame;
	return f;
}

void derefFrame(jscontext *cx, jsframe *f) {
	f->refCount--;
	if (!f->refCount) {
		if (f->outer)
			derefFrame(cx, f->outer);
		
		struct jsstackroot *p;
		while (p = f->stackRoots) {
			f->stackRoots = p->next;
			free(p);
		}
		
		free(f->slots);
		free(f->callFrame);
		free(f);
		
		cx->objcount--;
	}
}

void leaveFrame(jscontext *cx, jsframe *f) {
//	captureStack(cx);
	
	cx->topOfStack = f->previousFrame;
	cx->currentDepth--;

	derefFrame(cx, f);
	if (stepMode == 3) {
		trace(cx);
	}
}

void copyParams(jsframe *f, int paramCount, jsvalue *argv, int argc) {
	for (int idx = 0; idx < paramCount && idx < argc; idx++) {
		f->slots[idx] = argv[idx];
	}
}

static void jsmarkFrame(jscontext *cx, jsframe *frame) {
	for (int idx = 0; idx < frame->slotCount; idx++)
		jsmark(cx, frame->slots[idx]);
	for (int idx = 0; idx < frame->callFrameCount; idx++)
		jsmark(cx, frame->callFrame[idx]);
	for (struct jsstackroot *p = frame->stackRoots; p; p = p->next)
		jsmark(cx, p->val);
	if (frame->outer)
		jsmarkFrame(cx, frame->outer);
}

static void jsmarkObject(jscontext *cx, jsobject *obj) {
	if (obj->flags & FLAG_DELETED)
		jserror(cx, "Internal GC error (accessing deleted object)");
	if (obj->flags & FLAG_MARK)
		return;
	obj->flags |= FLAG_MARK;
	if (obj->cls)
		jsmarkObject(cx, (jsobject *) obj->cls);
	for (jsfield *field = obj->fields; field; field = field->next) {
		jsmark(cx, field->field);
		jsmark(cx, field->val);
	}
	
	if (obj->flags & FLAG_ARRAY) {
		jsarray *arr = (jsarray *) obj;
		for (int idx = 0; idx < arr->count; idx++)
			jsmark(cx, arr->content[idx]);
	} else if (obj->flags & FLAG_FUNCTION) {
		jsfunction *fn = (jsfunction *) obj;
		if (fn->outer)
			jsmarkFrame(cx, fn->outer);
		if (fn->prototype)
			jsmarkObject(cx, fn->prototype);
	}
}

void jsmark(jscontext *cx, jsvalue v) {
	if (!v)
		return;
	if (JSVAL_IS_OBJECT(v)) {
		jsobject *obj = JSVAL_TO_OBJECT(v);
		jsmarkObject(cx, obj);
	} else if (JSVAL_IS_DOUBLE(v)) {
		jsdouble *dbl = JSVAL_TO_DOUBLE(v);
		dbl->flags |= FLAG_MARK;
	} else if (JSVAL_IS_STRING(v)) {
		jsstring *str = JSVAL_TO_STRING(v);
		str->flags |= FLAG_MARK;
	}
}


void jsgc(jscontext *cx) {
	cx->objusage = 0;
	// mark phase
	for (int idx = 0; idx < cx->slotCount; idx++)
		jsmark(cx, cx->slots[idx]);
	
	for (jsframe *frame = cx->topOfStack; frame != NULL; frame = frame->previousFrame) {
		jsmarkFrame(cx, frame);
	}
	
	// mark roots
	for (struct jsroot *root = cx->roots; root; root = root->next)
	{
		for (int idx = 0; idx < root->count; idx++)
			jsmark(cx, root->ptr[idx]);
	}
	
	// sweep phase
	
	// sweep objects
	jsobject *obj = cx->firstObject;
	while (obj != NULL) {
		jsobject *next = obj->nextObject;
		
		if (!(obj->flags & FLAG_CONSTANT) && !(obj->flags & FLAG_MARK)) {
			// run any intrinsic destructors
			jsobject *cls = (jsobject *) obj->cls;
			while (cls != nil && isFunctionObj(cls)) {
				jsfunction *clsfn = (jsfunction *) cls;
				if (clsfn->destructor != nil)
					clsfn->destructor(cx, (jsvalue) obj);
				cls = (jsobject *) clsfn->prototype->cls;
			}
			
			
			cx->objcount--;
			obj->flags |= FLAG_DELETED;
			if (obj->prevObject)
				obj->prevObject->nextObject = next;
			else
				cx->firstObject = next;
			
			if (next)
				next->prevObject = obj->prevObject;
			
			if (obj->flags & FLAG_ARRAY) {
				jsarray *arr = (jsarray *) obj;
				obj->nextObject = (jsobject *) cx->freeArray;
				cx->freeArray = arr;
				free(arr->content);
			} else if (obj->flags & FLAG_FUNCTION) {
				jsfunction *fn = (jsfunction *) obj;
				fn->base.nextObject = (jsobject *) cx->freeFunction;
				cx->freeFunction = fn;
				if (fn->outer)
					derefFrame(cx, fn->outer);
			} else {
				obj->nextObject = cx->freeObject;
				cx->freeObject = obj;
			}
			
			jsfield *f;
			while (f = obj->fields) {
				obj->fields = f->next;
				f->next = cx->freeField;
				cx->freeField = f;
			}
			
		} else {
			obj->flags &= ~FLAG_MARK;
			cx->objusage++;
		}
		obj = next;
	}
	
	// sweep doubles
	jsdouble *p = cx->firstDouble;
	while (p != NULL) {
		jsdouble *next = p->nextDouble;
		
		if (!(p->flags & FLAG_CONSTANT) && !(p->flags & FLAG_MARK)) {
			cx->objcount--;
			p->flags |= FLAG_DELETED;
			if (p->prevDouble)
				p->prevDouble->nextDouble = next;
			else
				cx->firstDouble = next;
			
			if (next)
				next->prevDouble = p->prevDouble;
			
			p->nextDouble = cx->freeDouble;
			cx->freeDouble = p;
		} else {
			p->flags &= ~FLAG_MARK;
			cx->objusage++;
		}
		p = next;
	}
	
	// sweep strings
	jsstring *str = cx->firstString;
	while (str != NULL) {
		jsstring *next = str->nextString;
		
		if (!(str->flags & FLAG_CONSTANT) && !(str->flags & FLAG_MARK)) {
			cx->objcount--;
			str->flags |= FLAG_DELETED;
			if (str->prevString)
				str->prevString->nextString = next;
			else
				cx->firstString = next;
			
			if (next)
				next->prevString = str->prevString;
			
#ifdef MACOSX
			if (str->nsstr)
				[str->nsstr release];
#endif
			free(str->chars);
			
			str->nextString = cx->freeString;
			cx->freeString = str;
		} else {
			str->flags &= ~FLAG_MARK;
			cx->objusage++;
		}
		str = next;
	}
	
	if (2 * cx->objcount > cx->maxobjcount)
		cx->maxobjcount = 2 * cx->objcount;
}

int validateObject(jsvalue v) {
	/*
	if (v) {
		jsobject *obj = (jsobject *) v;
		if (obj->flags & FLAG_DELETED)
			jserror(cx, "Internal GC error (accessing deleted object)");
	}
	 */
	return 1;
}

void jsaddstackroot(jscontext *cx, jsvalue ref) {
	struct jsstackroot *p = malloc(sizeof(struct jsstackroot));
	p->next = cx->topOfStack->stackRoots;
	cx->topOfStack->stackRoots = p;
	p->val = ref;
}

void jsaddroot(jscontext *cx, jsvalue *ref) {
	struct jsroot *p = (struct jsroot *) malloc(sizeof(struct jsroot));
	p->ptr = ref;
	p->count = 1;
	p->next = cx->roots;
	cx->roots = p;
}

void jsaddrootmulti(jscontext *cx, jsvalue *ref, int count) {
	struct jsroot *p = (struct jsroot *) malloc(sizeof(struct jsroot));
	p->ptr = ref;
	p->count = count;
	p->next = cx->roots;
	cx->roots = p;
}

void jsremoveroot(jscontext *cx, jsvalue *ref) {
	struct jsroot *p, *last;
	last = nil;
	for (p = cx->roots; p; p = p->next) {
		if (p->ptr == ref) {
			if (last)
				last->next = p->next;
			else
				cx->roots = p->next;
			free(p);
			return;
		}
		last = p;
	}
}

int equalStringsJC(jsstring *a, const char *b)
{
	int idx;
	for (idx = 0; idx<a->length && b[idx]; idx++) {
		unsigned short cha = a->chars[idx];
		unsigned short chb = b[idx];
		if (cha != chb) return 0;
	}
	if (idx == a->length && !b[idx])
		return 1;
	return 0;
}


int compareStrings(jsstring *a, jsstring *b)
{
	int idx;
	for (idx = 0; idx<a->length && idx < b->length; idx++) {
		unsigned short cha = a->chars[idx];
		unsigned short chb = b->chars[idx];
		if (cha < chb) return -1;
		if (cha > chb) return 1;
	}
	if (idx < b->length) return -1;
	if (idx < a->length) return 1;
	return 0;
}

int equalStrings(jsstring *a, jsstring *b)
{
	return 0 == compareStrings(a, b);
}

jsvalue jsstrFromChars(jscontext *cx, unsigned short *chars, int length) {
	jsstring *str;
	
	cx->objcount++;
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	str->length = length;
	str->chars = chars;
#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = 0;
	
	str->prevString = NULL;
	str->nextString = cx->firstString;
	
	if (cx->firstString)
		cx->firstString->prevString = str;
	
	cx->firstString = str;
	
	return STRING_TO_JSVAL(str);
}

jsvalue jsstrFromConst(jscontext *cx, void *chars) {
	wchar_t *cstr = chars;
	jsstring *str;
	
	int len = 0;
	while (cstr[len++])
		;
	
	cx->objcount++;
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	str->length = len;
	str->chars = malloc(len * 2);
	for (int idx = 0; idx < len; idx++)
		str->chars[idx] = cstr[idx];
#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = 0;
	
	str->prevString = NULL;
	str->nextString = cx->firstString;
	
	if (cx->firstString)
		cx->firstString->prevString = str;
	
	cx->firstString = str;
	
	return STRING_TO_JSVAL(str);
}

jsvalue jsstrFromCstring(jscontext *cx, const char *cstr) {
	jsstring *str;
	
	int len = 0;
	while (cstr[len])
		len++;
	
	cx->objcount++;
	if (str = cx->freeString)
		cx->freeString = str->nextString;
	else
		str = fmalloc(cx, sizeof(jsstring));
	str->length = len;
	str->chars = malloc(len * 2);
	for (int idx = 0; idx < len; idx++)
		str->chars[idx] = (unsigned char) (cstr[idx]) & 0xff;
#ifdef MACOSX
	str->nsstr = nil;
#endif
	str->flags = 0;
	
	str->prevString = NULL;
	str->nextString = cx->firstString;
	
	if (cx->firstString)
		cx->firstString->prevString = str;
	
	cx->firstString = str;
	
	return STRING_TO_JSVAL(str);
}

jsvalue jsloadimage(jscontext *cx, const char *imgName) {
#ifdef MACOSX
	NSString *name = [NSString stringWithCString:imgName];
	NSString *bundle = [[NSBundle mainBundle] pathForResource:[name stringByDeletingPathExtension] ofType:[name pathExtension]];
	if (bundle) {
		NSImage *img = [[NSImage alloc] initWithContentsOfFile: bundle];
		if (img)
			return OBJECT_TO_JSVAL(bindImage(cx, img));
	}
#endif
	return JSVAL_NULL;
}

